// (C) 2022 Intel Corporation. All rights reserved.
// Your use of Intel Corporation's design tools, logic functions and other 
// software and tools, and its AMPP partner logic functions, and any output 
// files from any of the foregoing (including device programming or simulation 
// files), and any associated documentation or information are expressly subject 
// to the terms and conditions of the Intel Program License Subscription 
// Agreement, Intel FPGA IP License Agreement, or other applicable 
// license agreement, including, without limitation, that your use is for the 
// sole purpose of programming logic devices manufactured by Intel and sold by 
// Intel or its authorized distributors.  Please refer to the applicable 
// agreement for further details.

module gcm
(
  input  logic         clk,
  input  logic         reset_n,
  input  logic         aes_flush_done,
  input  logic         aes_op,
  input  logic         ct_valid,
  input  logic [127:0] ct_data,
  input  logic         multH_valid,
  input  logic [127:0] multH,
  input  logic         last_data,
  
  output logic [127:0] auth_tag,
  output logic         auth_tag_valid
);

 typedef enum logic [1:0] {
  CT_VALID       ,
  SHIFT_CT       ,
  GHASH_REDUCION ,
  AUTH_TAG_VALID
} state_type; 

 state_type state_gcm;

 logic [127:0] ct_data_reg;
 logic [15:0] ct_16bit;
 logic ct_16bit_en;
 logic [2:0] shift_ct_cnt;
 logic [255:0] reg0, reg1, reg2;
 logic [143:0] out;
 logic [127:0] ghash_red_out;
 logic ghash_red_valid;

 assign reg0[255:112] = out;
 assign reg0[111:0] = 'b0;
 assign reg2 = reg0 ^ reg1;

 always @(posedge clk or negedge reset_n)
 begin
   if (reset_n == 1'b0)
   begin
     state_gcm       <= CT_VALID;
     ct_16bit_en     <= 1'b0;
     ct_data_reg     <= 128'h0;
     ct_16bit        <= 16'h0;
     shift_ct_cnt    <= 3'b000;
     reg1            <= 'b0;
     ghash_red_valid <= 1'b0;
   end
   else
   begin
     if (aes_flush_done == 1'b1)
     begin
       state_gcm       <= CT_VALID;
       ct_16bit_en     <= 1'b0;
       ct_data_reg     <= 128'h0;
       ct_16bit        <= 16'h0;
       shift_ct_cnt    <= 3'b000;
       reg1            <= 'b0;
       ghash_red_valid <= 1'b0;
     end
     else
     begin
       case (state_gcm)
         CT_VALID : begin
             if (ct_valid == 1'b1)
             begin
               state_gcm   <= SHIFT_CT;
               ct_16bit    <= ct_data[15:0];
               ct_16bit_en <= 1'b1;
               ct_data_reg <= ct_data;
             end
             else
             begin
               state_gcm   <= CT_VALID;
               ct_16bit    <= 16'h0;
               ct_16bit_en <= 1'b0;
               ct_data_reg <= 128'h0;
             end
             shift_ct_cnt    <= 3'b000;
             reg1            <= reg0 >> 16;
             ghash_red_valid <= 1'b0;
         end
         SHIFT_CT : begin
             if (shift_ct_cnt == 3'b111)
             begin
               if (last_data == 1'b1) 
               begin
                 state_gcm <= GHASH_REDUCION;
               end
               else 
               begin
                 state_gcm <= CT_VALID;
               end
             end
             else
             begin
               state_gcm <= SHIFT_CT;
             end
             shift_ct_cnt    <= shift_ct_cnt + 1;
             ghash_red_valid <= 1'b0;
             case (shift_ct_cnt)
                3'b000 : begin
                   ct_16bit    <= ct_data_reg[31:16];
                   ct_16bit_en <= 1'b1;
                   reg1        <= reg2 >> 16;
                end
                3'b001 : begin
                   ct_16bit    <= ct_data_reg[47:32];
                   ct_16bit_en <= 1'b1;
                   reg1        <= reg2 >> 32;
                end
                3'b010 : begin
                   ct_16bit    <= ct_data_reg[63:48];
                   ct_16bit_en <= 1'b1;
                   reg1        <= reg2 >> 48;
                end
                3'b011 : begin
                   ct_16bit    <= ct_data_reg[79:64];
                   ct_16bit_en <= 1'b1;
                   reg1        <= reg2 >> 64;
                end
                3'b100 : begin
                   ct_16bit    <= ct_data_reg[95:80];
                   ct_16bit_en <= 1'b1;
                   reg1        <= reg2 >> 80;
                end
                3'b101 : begin
                   ct_16bit    <= ct_data_reg[111:96];
                   ct_16bit_en <= 1'b1;
                   reg1        <= reg2 >> 96;
                end
                3'b110 : begin
                   ct_16bit    <= ct_data_reg[127:112];
                   ct_16bit_en <= 1'b1;
                   reg1        <= reg2 >> 112;
                end
                3'b111 : begin
                   reg1        <= reg2 >> 128;
                   ct_16bit    <= ct_data_reg[127:112];
                   ct_16bit_en <= 1'b1;
                end

             endcase
         end
         GHASH_REDUCION : begin
             state_gcm       <= AUTH_TAG_VALID;
             ghash_red_valid <= 1'b1;
         end
         AUTH_TAG_VALID : begin
             state_gcm       <= CT_VALID;
             ghash_red_valid <= 1'b1;
         end
         default : begin
             ghash_red_valid <= 1'b0;
             state_gcm       <= CT_VALID;
         end

       endcase
     end//end of aes_flush_done
   end
 end

 always @(posedge clk or negedge reset_n)
 begin
   if (reset_n == 1'b0)
   begin
     auth_tag        <= 128'h0;
     auth_tag_valid  <= 1'b0;
   end
   else
   begin
     if (aes_flush_done == 1'b1)
     begin
       auth_tag        <= 128'h0;
       auth_tag_valid  <= 1'b0;
     end
     else
     begin
       if (ghash_red_valid == 1'b1)
       begin
         auth_tag        <= ghash_red_out;
         auth_tag_valid  <= 1'b1;
       end
     end
   end
 end

gfmul_128_16 inst_gfmul_128_16(
   .a  (multH),
   .b  (ct_16bit),
   .out(out)
 );

ghash_red_32b inst_ghash_red_32b (
  .in((ghash_red_valid == 1'b1) ? reg2[255:96] : 'b0),
  .out(ghash_red_out)
);
endmodule
